#include "tcp_acceptor.h"

#include "tcp_server.h"

namespace qio {

TcpAcceptor::~TcpAcceptor() noexcept { delete fd_chan_; }

TcpAcceptor::TcpAcceptor(EventLoop *loop, UniqueConfig &&conf) noexcept
    : loop_(loop),
      acceptor_conf_(std::forward<UniqueConfig>(conf)),
      fd_chan_(new FdChan(loop_, acceptor_conf_->fd)),
      next_conn_id_(0xFFFF),
      conn_map_() {
  assert(acceptor_conf_);
  InjectConnDisconnectProxy();
  ConfigAcceptChan();
}

void TcpAcceptor::Start() noexcept {
  assert(loop_->IsInLoop());
  // start
  fd_chan_->Start();
  // prepare events
  fd_chan_->EnableRead();
  fd_chan_->DisableWrite();
}

void TcpAcceptor::Close() noexcept {
  assert(loop_->IsInLoop());
  fd_chan_->Close();
}

void TcpAcceptor::InjectConnDisconnectProxy() noexcept {
  auto &origin_callbacks = acceptor_conf_->conn_callbacks;
  auto old_disconn_cb = origin_callbacks.disconn_cb;
  // delegate disconnect callback
  origin_callbacks.disconn_cb =
      std::bind(&TcpAcceptor::ConnDisconnectedProxy, this,
                std::placeholders::_1, old_disconn_cb);
}

void TcpAcceptor::ConfigAcceptChan() noexcept {
  //
  fd_chan_->SetOnReadWriteCallback(
      std::bind(&TcpAcceptor::HandleAcceptEvents, this, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3));

  fd_chan_->SetOnClosedCallback(
      std::bind(&TcpAcceptor::HandleFdClosed, this, std::placeholders::_1));

  fd_chan_->SetOnErrorCallback(
      std::bind(&TcpAcceptor::HandleFdError, this, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3));
}

void TcpAcceptor::HandleAcceptEvents(FdChan *, Fd fd, int events) noexcept {
  struct sockaddr_in in_addr {};
  socklen_t in_addr_len = sizeof in_addr;
  Fd new_fd = socket_accept(fd, reinterpret_cast<struct sockaddr *>(&in_addr),
                            &in_addr_len);
  if (new_fd == INVALID_SOCKET) {
    const auto socket_error = GetLastSocketError();
    if (!IO_ERR_ACCEPT_RETRIABLE(socket_error)) {  // NOLINT
      acceptor_conf_->acceptor_callbacks.OnError(this, socket_error,
                                                 strerror(socket_error));
    } else {
      // accept 错误可以被忽略,不返回错误给用户
    }
    return;
  }
  IPAddress remote_addr = ParseSockAddrName4(&in_addr);
  auto conn_id = GetNextConnId();

  auto cfg = std::make_unique<TcpConn::Config>();
  cfg->fd = new_fd;
  cfg->conn_id = conn_id;
  cfg->remote_addr = std::move(remote_addr);
  cfg->mode = TcpConn::SERVER;
  cfg->read_buffer = acceptor_conf_->io_buffer;
  cfg->read_buffer_len = acceptor_conf_->io_buffer_len;
  cfg->read_cache_buffer = acceptor_conf_->GenerateBuffer();
  cfg->callbacks = acceptor_conf_->conn_callbacks;
  cfg->ssl_helper = acceptor_conf_->ssl_helper;

  auto shared_tcp_conn = std::make_shared<TcpConn>(loop_, std::move(cfg));
  auto timeout_hd = acceptor_conf_->GenerateTimeoutHandle(shared_tcp_conn);
  shared_tcp_conn->SetTimeoutHandle(timeout_hd);
  conn_map_.insert({conn_id, shared_tcp_conn});
  shared_tcp_conn->Start();
}

void TcpAcceptor::HandleFdClosed(FdChan *) noexcept {
  // close all conns
  std::for_each(conn_map_.cbegin(), conn_map_.cend(),
                [](const std::pair<const std::uint64_t, SharedTcpConn> &v) {
                  if (v.second) {
                    v.second->Close();
                  }
                });
  // acceptor closed
  acceptor_conf_->acceptor_callbacks.OnClosed(this);
  // reset conn_callbacks
  acceptor_conf_->acceptor_callbacks.Reset();
}

void TcpAcceptor::HandleFdError(FdChan *, int err_code,
                                const char *err_msg) noexcept {
  acceptor_conf_->acceptor_callbacks.OnError(this, err_code, err_msg);
}

void TcpAcceptor::ConnDisconnectedProxy(
    TcpConn *conn, const TcpConn::OnDisconnectedCallback &old_cb) noexcept {
  if (old_cb) {
    old_cb(conn);
  }
  // remove connection
  const auto erase_num = conn_map_.erase(conn->GetConnId());
  assert(erase_num == 1);
}

ConnId TcpAcceptor::GetNextConnId() noexcept {
  constexpr static auto u64_max = (std::numeric_limits<std::uint64_t>::max)();
  ConnId conn_id = 0;
  do {
    if (next_conn_id_ == u64_max) {
      next_conn_id_ = 0xFFFF;
    }
    conn_id = ++next_conn_id_;
    if (conn_map_.count(conn_id) == 0) {
      break;
    }
  } while (true);
  return conn_id;
}

}  // namespace qio
